package hub

import (
	"FeedXmh/app/dao"
	"FeedXmh/app/model"
	"FeedXmh/redis"
	"FeedXmh/tdengine"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/encoding/gbinary"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/encoding/gparser"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"github.com/gogf/gf/util/grand"
	"math"
	"paco_toolkit/DMXFGW"
	"paco_toolkit/common"
	"strconv"
	"time"
)

func StartHub() {
	g.Log().Line(true).Debug("Start Hub")
	//ConnetHub()
	client := CreateMqttClient("cid_Hub_HZ_"+grand.Digits(10), onConnet, onConnetLost)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		g.Log().Line(true).Error(token.Error())
	}

	var str string
	bytes := gbinary.LeEncodeUint16(0x6401)
	byte_hight := bytes[1]
	byte_low := bytes[0]
	if byte_hight < 100 {
		str = gconv.String(byte_hight) + common.PadLeft(gconv.String(byte_low), 2, "0")
	} else {
		str = string(byte_hight-100+'A') + common.PadLeft(gconv.String(byte_low), 2, "0")
	}

	g.Log().Line(true).Debugf("Hnum=%s", str)
}

func onConnet(client mqtt.Client) {
	g.Log().Line(true).Debug("onConnet")
	debug_mode := g.Cfg().GetBool("basic.DebugMode")
	if debug_mode {
		//本地测试
		SubscribeGW(client)
		//Subscribe_SBT(client)
	} else {
		//生产环境
		SubscribeGW(client)
		//Subscribe_SBT(client)
	}
}
func onConnetLost(client mqtt.Client, err error) {
	g.Log().Line(true).Debug("onConnetLost")
}

func Allocate(mode string) {
	//mode:="FeedM"
	c := CreateMqttClient("cid_Allocate_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Println(token.Error().Error())
	}
	var data []byte
	topic_s := "S/" + mode + "/0"

	//设置地址
	var daddr uint32
	daddr = dao.GetMaxDaddr(mode) + 1
	daddr_hex := gbinary.EncodeUint32(daddr)
	//校时日期
	var datetime = gtime.Now().Format("ymdHis")
	clock, err := hex.DecodeString(datetime)
	if err != nil {
		g.Log().Println(err)
	}

	data = append(data, daddr_hex...)
	data = append(data, clock...)

	var msg []byte
	msg = DMXFGW.CreateMsg(0x03, 4, uint32(len(data)), data)

	g.Log().Line(true).Debugf("msg  % X", msg)
	g.Log().Line(true).Debugf("msg size = %d", len(msg))
	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()
	time.Sleep(1 * time.Second)
	c.Disconnect(250)
}

func SubscribeGW(client mqtt.Client) {
	topic_p := "P/DMXFGW/#"
	debug_mode := g.Cfg().GetBool("basic.DebugMode")
	if debug_mode {
		//topic_p = "P/DMXFGW/60005654"
		topic_p = "P/DMXFGW/20000031"
		//topic_p = "P/DMXFGW/#"
	} else {
		topic_p = "P/DMXFGW/#"
	}
	// 订阅主题
	if token := client.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(true).Debug("Gw recv from topic:" + msg.Topic())
		g.Log().Line(true).Debug("Gw recv from addr:" + addr)
		g.Log().Line(true).Debugf("Gw recv msg len: %d", len(msg.Payload()))
		//g.Log().Line(true).Infof("Gw recv msg ->% X", msg.Payload())
		DecodeNMSWQGWewPack(msg.Payload(), addr)

	}); token.Wait() && token.Error() != nil {
		g.Log().Line(true).Error(token.Error().Error())
	}
}

func DecodeNMSWQGWewPack(data []byte, addr string) {

	if gtime.Now().Hour() == 23 && gtime.Now().Minute() > 58 {
		return
	}
	if gtime.Now().Hour() == 0 && gtime.Now().Minute() < 2 {
		return
	}

	if pack, err := DMXFGW.DecodePack(data); err != nil {
		g.Log().Line(true).Error(err.Error())
	} else {
		var c = pack.C

		if c == 0x80 {
			handParamNew(pack, addr)
		} else if c == 0x82 {
			go handStatusNew(pack, addr)
		} else if c == 0x83 {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if Addr == 0x04 && pack.Data[0] == 0x00 {
				g.Log().Line(true).Debugf("Gw recv msg %s->% X", addr, data)
				handAllocateNew(pack, addr)
			} else if Addr == 0x05 {
				g.Log().Line(true).Debugf("Gw recv msg ->% X", data)
				handRecordNew(pack, addr)
			}
		}
	}
}

func handStatusNew(pack *DMXFGW.Pack, addr string) {

	_, err := dao.T_device.GetOne(fmt.Sprintf(" device_addr='%s' ", addr))
	if err != nil {
		g.Log().Line(true).Debugf(" addr=%s not create topo!", addr)
		return
	}

	busy_flag := redis.GetGwBusyFlag(addr)
	if busy_flag > 0 && busy_flag < 5 {
		g.Log().Line(true).Warningf("Gw %s handStatusNew waiting......", addr)
		busy_flag++
		redis.SetGwBusyFlag(addr, busy_flag)
		return
	}

	//执行完成，释放
	defer redis.SetGwBusyFlag(addr, 0)

	g.Log().Line(true).Infof("Gw %s handStatusNew...", addr)

	today := gtime.Now().Format("Y-m-d")

	status, err := DMXFGW.DecodeState(pack.Data)
	if err != nil {
		g.Log().Line(true).Error(err.Error())
	} else {
		db := g.DB(g.Cfg().GetString("custom.db_name"))

		var node_version_arr []string
		//node_num := 0
		online_node_num := 0

		needUpdateNodePara := false //对比crc16是否需要更新参数
		f_status := DMXFGW.FormatState(status)

		//保存饲喂器 每日历史记录
		var node_status_update_list gdb.List
		var node_status_add_list gdb.List
		var node_status_his_list gdb.List
		var node_status_redis_update_list g.List

		for i := 0; i < DMXFGW.NUM_MP; i++ {
			if gconv.Uint32(f_status.Enable[i]) > 0 && gconv.Uint32(f_status.NodeSN[i]) > 0 {
				sn := f_status.NodeSN[i]
				online := f_status.Online[i]
				mode := status.NodeStates[i].Mode

				//判断节点类型，根据类型处理
				if mode < DMXFGW.MODE_GW || mode == DMXFGW.MODE_FG {
					//饲喂器+粥料器
					_model := ""
					if mode < DMXFGW.MODE_GW {
						_model = "feed"
					} else if mode == DMXFGW.MODE_FG {
						_model = "fg"
						mode = uint8(gconv.Int(f_status.NodeFgStates[i].Mode))
					}

					//饲喂器+粥料器(状态结构一致)，用model区分
					alarm := f_status.NodeStates[i].AlarmState
					feed_water := f_status.NodeStates[i].FeedWater
					day := f_status.NodeStates[i].Gday
					//mode := f_status.NodeStates[i].Mode
					max_feed := f_status.NodeStates[i].MaxFeed
					day_use_water_auto := f_status.NodeStates[i].DayUseWaterAuto
					day_use_feed_auto := f_status.NodeStates[i].DayUseFeedAuto
					day_use_water_man := f_status.NodeStates[i].DayUseWaterMan
					day_use_feed_man := f_status.NodeStates[i].DayUseFeedMan
					date_time, err := gtime.StrToTime("20" + f_status.NodeStates[i].DateTime)
					if err != nil {
						date_time = gtime.Now()
					}
					version := f_status.NodeStates[i].Version

					//online状态字
					var old_online string
					val := redis.GetGwNodeAliveVal(addr, sn)
					if val == nil {
						//不存在，则表示初次
					} else {
						old_online = gconv.String(val)
					}

					handNodeOnline(addr, sn, old_online, online)

					handNodeAlarm(addr, sn, alarm)

					//2023-10-10 如果是保育育肥，目标下料=当前下料
					if _model == "feed" {
						if gconv.Int(mode) == DMXFGW.FEED_MODE_FAT || gconv.Int(mode) == DMXFGW.FEED_MODE_NURSE {
							max_feed = fmt.Sprintf("%.2f", gconv.Float32(day_use_feed_man)+gconv.Float32(day_use_feed_auto))
						}
					} else if _model == "fg" {
						if gconv.Int(mode) == DMXFGW.FG_MODE_FAT || gconv.Int(mode) == DMXFGW.FG_MODE_NURSE {
							max_feed = fmt.Sprintf("%.2f", gconv.Float32(day_use_feed_man)+gconv.Float32(day_use_feed_auto))
						}
					}

					if gconv.Int(online) > 0 {
						online_node_num++
					}

					repeat := false
					for _, v := range node_version_arr {
						if version == v {
							repeat = true
							break
						}
					}
					if !repeat {
						node_version_arr = append(node_version_arr, version)
					}

					s_data, _ := gparser.VarToJsonString(f_status.NodeStates[i])

					//对比是否需要更新饲喂器的第一次上线时间到redis
					exist, _ := redis.GetGwNodeFirstOnline(addr, sn)
					if !exist {
						//不存在,获取最早在线时间，更新
						var first_online string
						record, _ := db.GetOne(fmt.Sprintf("SELECT date_time FROM t_hzfgw_node_status_his WHERE addr='%s' AND sn='%s' ORDER BY date_time asc LIMIT 1;", addr, sn))
						if record != nil {
							his := record.Map()
							first_online = gconv.String(his["date_time"])
						} else {
							//设置为当前时间
							first_online = gtime.Now().Format(common.DATE_TIME_FORMAT)
						}
						redis.SetGwNodeFirstOnline(addr, sn, first_online)
					}

					//todo
					//2024-06-20 当前状态缓存redis，每十分钟入库到mysql

					//对比redis本地状态，一致则不更新status(排除掉DateTime,Adval影响)，DateTime相差大于20分钟也强制刷新
					_status_new := f_status.NodeStates[i]
					_status_new.DateTime = ""
					_status_new.Adval = ""
					status_new, _ := gparser.VarToJsonString(_status_new)
					status_old := redis.GetGwNodeStatus(addr, sn)

					//2024-05-08 停用模式下要强制刷新状态
					if status_old == status_new && old_online == online && gconv.Int(mode) > 0 {
						g.Log().Line(true).Debugf("addr='%s' and sn='%s' node status not change", addr, sn)
					} else {

						//redis.SetNMSWQGWNodeStatus(addr, sn, status_new)

						//历史记录

						his := gdb.Map{}
						one, err := dao.T_hzfgw_node_status_his.GetOne(addr, sn, today)
						if err != nil {
							g.Log().Line(true).Error(err.Error())
						} else {
							if one != nil {
								//当天历史已经存在，更新
								//his["sn"] = sn
								his = one
							} else {
								//新增
							}
							//2024-03-24 T_hzfgw_node_status_his 须保持猪只数目
							//2024-03-26 T_hzfgw_node_status_his 保存采食量相关数据
							pig_num := gconv.Int(redis.GetGwNodePigNum(addr, sn))
							son_num := gconv.Int(redis.GetGwNodeSonNum(addr, sn))
							_mode := gconv.Int(mode)
							if pig_num == 0 && _mode > 0 {
								pig_num = 1
							}
							day_use_feed := gconv.Float32(day_use_feed_man) + gconv.Float32(day_use_feed_auto)
							var avg_feed float32
							if pig_num > 0 {
								avg_feed = day_use_feed / float32(pig_num)
							}
							var health string
							var low, standard, high float32
							low, standard, high = GetStandard(_model, _mode, gconv.Int(day))
							if low <= avg_feed && avg_feed <= high {
								//正常采食
								health = "good"
							} else {
								if low > avg_feed {
									//低采食
									health = "low"
								} else if high < avg_feed {
									//高采食
									health = "high"
								}
							}
							his["son_num"] = son_num
							his["pig_num"] = pig_num
							his["avg_feed"] = avg_feed
							his["low_standard"] = low
							his["high_standard"] = high
							his["standard"] = standard
							his["health"] = health

							his["addr"] = addr
							his["sn"] = sn
							his["model"] = _model
							his["online"] = online
							his["alarm"] = alarm
							his["feed_water"] = feed_water
							his["day"] = day
							his["mode"] = mode
							his["max_feed"] = max_feed
							his["day_use_water_auto"] = day_use_water_auto
							his["day_use_feed_auto"] = day_use_feed_auto
							his["day_use_water_man"] = day_use_water_man
							his["day_use_feed_man"] = day_use_feed_man
							his["date_time"] = date_time
							his["version"] = version
							his["lasttime"] = gtime.Now().String()
							his["his_date"] = today
							node_status_his_list = append(node_status_his_list, his)
						}

						//状态更新
						items, err := dao.T_hzfgw_node_status.GetList(fmt.Sprintf(" addr='%s' and sn='%s' ", addr, sn))
						if err != nil {
							g.Log().Line(true).Warningf("sn='%s' dao.T_hzfgw_node_status.GetList err!", sn)
							g.Log().Line().Error(err)
							//break
						} else {
							var update bool
							item := gdb.Map{}
							if len(items) > 0 {
								//更新
								update = true
								item = items[0]
							} else {
								//新增
								update = false
							}
							item["addr"] = addr
							item["sn"] = sn
							item["model"] = _model
							item["online"] = online
							item["alarm"] = alarm
							item["feed_water"] = feed_water
							item["day"] = day
							item["mode"] = mode
							item["max_feed"] = max_feed
							item["day_use_water_auto"] = day_use_water_auto
							item["day_use_feed_auto"] = day_use_feed_auto
							item["day_use_water_man"] = day_use_water_man
							item["day_use_feed_man"] = day_use_feed_man
							item["date_time"] = date_time
							item["version"] = version
							item["data"] = s_data
							item["lasttime"] = gtime.Now().String()

							if update {
								node_status_update_list = append(node_status_update_list, item)
							} else {
								node_status_add_list = append(node_status_add_list, item)
							}

							redis_update_item := g.Map{
								"addr":       addr,
								"sn":         sn,
								"status_new": status_new,
							}
							node_status_redis_update_list = append(node_status_redis_update_list, redis_update_item)
						}

						//对比crc16是否需要更新参数
						if !needUpdateNodePara {
							crc16 := redis.GetGwNodeCrc16(addr, sn)
							if crc16 != f_status.NodeStates[i].Crc16 {
								g.Log().Line(true).Infof(" addr='%s' and sn='%s' redis crc16=%s,para crc16=%s,need update para!!", addr, sn, crc16, f_status.NodeStates[i].Crc16)
								needUpdateNodePara = true
							}
						}

						//更新crc16
						redis.SetGwNodeCrc16(addr, sn, f_status.NodeStates[i].Crc16)
					}
				}

			}
		}

		if len(node_version_arr) > 0 {
			redis.SetGwAllNodeVersion(addr, gstr.Join(node_version_arr, ","))
		}

		redis.SetGwonlineNodeNum(addr, gconv.String(online_node_num))

		//保存node_status_list node_status_his_list
		if len(node_status_his_list) > 0 {
			isOk, err := dao.T_hzfgw_node_status_his.SaveList(node_status_his_list)
			if !isOk {
				g.Log().Line(true).Error(err.Error())
			} else {
				g.Log().Line(true).Debugf("save DMXFGW %s num=%d to t_hzfgw_node_status_his ok", addr, len(node_status_his_list))
			}
		}

		no_error := true
		node_status_num := len(node_status_update_list) + len(node_status_add_list)
		if len(node_status_update_list) > 0 {
			isOk, err := dao.T_hzfgw_node_status.Update(node_status_update_list)
			if !isOk {
				g.Log().Line(true).Error(err.Error())
				no_error = false
			} else {
				g.Log().Line(true).Infof("update DMXFGW %s num=%d to t_hzfgw_node_status ok", addr, len(node_status_update_list))
			}
		}
		if len(node_status_add_list) > 0 {
			isOk, err := dao.T_hzfgw_node_status.Insert(node_status_add_list)
			if !isOk {
				g.Log().Line(true).Error(err.Error())
				no_error = false
			} else {
				g.Log().Line(true).Infof("insert DMXFGW %s num=%d to t_hzfgw_node_status ok", addr, len(node_status_add_list))
			}
		}

		if no_error && len(node_status_redis_update_list) > 0 && node_status_num > 0 {
			for _, item := range node_status_redis_update_list {
				sn := gconv.String(item["sn"])
				status_new := gconv.String(item["status_new"])
				redis.SetGwNodeStatus(addr, sn, status_new)
			}
		}

		//更新T_device
		handGwStatus(addr, f_status)

		//同步参数+记录
		if needUpdateNodePara {
			//读取新参数
			time.Sleep(2 * time.Second)
			g.Log().Line(true).Infof("read DMXFGW %s para...", addr)
			readNMSWQGWPara(addr)
			time.Sleep(5 * time.Second)
		}

		//同步饲喂器记录
		minid := gconv.Uint64(f_status.RecMinId)
		maxid := gconv.Uint64(f_status.RecMaxId)
		handSyncRecords(addr, minid, maxid)
	}

}

// 更新网关状态
func handGwStatus(addr string, f_status *DMXFGW.FState) {
	clients_data, _ := GetApiClients(gdb.Map{
		"_page":          1,
		"_limit":         100000,
		"_like_clientid": fmt.Sprintf("cid_%s_%s", model.GW_MODE, addr),
	})
	ip_address := ""
	if len(clients_data) > 0 {
		client := clients_data[0]
		ip_address = gconv.String(client["ip_address"])
	}
	isOk, err := dao.T_device.Update(model.GW_MODE, addr, 1, ip_address, gtime.Now().String(), f_status.Version, "0")
	if !isOk {
		g.Log().Line(true).Error(err.Error())
	} else {
		g.Log().Line(true).Debugf("save DMXFGW %s to t_device ok", addr)
	}
}
func handSyncRecords(addr string, minid uint64, maxid uint64) {

	redis.SetGwMinMaxRid(addr, minid, maxid) //缓存最大最小id
	g.Log().Line(true).Debugf(" DMXFGW %s minid=%d maxid=%d", addr, minid, maxid)
	rid := gconv.Uint64(redis.GetGwLastRid(addr))
	if rid == 0 {
		rid = tdengine.GetLastRid(addr)
	}
	//rid := tdengine.GetLastRid(addr)
	g.Log().Line(true).Debugf(" DMXFGW %s rid=%d", addr, rid)
	if minid > rid {
		//读取
		var begin uint64
		begin = minid
		readNMSWQGWRecords(addr, begin, 200)
	} else if minid <= rid && rid < maxid {
		//读取
		var begin uint64
		if rid == 0 {
			begin = 0
		} else {
			begin = rid + 1
		}
		readNMSWQGWRecords(addr, begin, 200)
	} else if maxid < rid {
		//控制器已经恢复出厂设置，清理记录
		g.Log().Line(true).Warningf(" factory reset ????  minid=%d maxid=%d rid=%d ", minid, maxid, rid)
		dao.T_hzfgw_record.Delete(fmt.Sprintf(" addr='%s' and rid>=%d ", addr, maxid))
		redis.SetGwLastRid(addr, gconv.String(maxid))
	}
}

func handNodeOnline(addr string, sn string, old_online string, online string) {

	if old_online != online {
		if 1 == 1 {
			//在线变化
			db := g.DB(g.Cfg().GetString("custom.db_name"))
			g.Log().Line(true).Infof("Gw %s,sn:%s:online old->%s online new->%s", addr, sn, old_online, online)
			houseid, farmid, _ := dao.T_device.GetDeviceParentID(model.GW_MODE, addr)

			if old_online == "1" && online == "0" {
				//离线
				hnum := redis.GetGwNodeHnum(addr, sn)
				if hnum == "" {
					hnum, _ = dao.T_hzfgw_node_para.GetHnum(addr, sn)
				}
				_, err := dao.T_alarm_now.AddAlarm(farmid, houseid, model.DMXFGW_NODE, addr, sn, model.ALARM_LEVEL_1, strconv.Itoa(model.ALARM_TYPE_SWQ), strconv.Itoa(model.ALARM_SUBTYPE_SWQ_OFFLINE), model.ALARM_SWQ_OFFLINE, hnum)
				if err != nil {
					g.Log().Line(true).Error(err)
				} else {
					g.Log().Line(true).Debugf("addr=%s sn=%s hnum=%s 新增离线告警", addr, sn, hnum)
				}
			} else {
				//恢复在线
				//2024-03-31 更新在线状态
				hnum := redis.GetGwNodeHnum(addr, sn)
				if hnum == "" {
					hnum, _ = dao.T_hzfgw_node_para.GetHnum(addr, sn)
				}
				db.Exec(fmt.Sprintf("update t_hzfgw_node_status set online=1 where sn='%s';", sn))
				g.Log().Line(true).Debugf("addr=%s sn=%s hnum=%s 恢复在线", addr, sn, hnum)
				item, err := dao.T_alarm_now.GetNodeHappenAlarm(addr, sn, model.ALARM_SWQ_OFFLINE)
				if err != nil {
					g.Log().Line(true).Error(err)
				} else {
					//消除告警
					if item != nil {
						_, err := dao.T_alarm_now.Delete(gconv.String(item["id"]))
						if err != nil {
							g.Log().Line(true).Error(err)
						} else {
							g.Log().Line(true).Debugf("addr=%s sn=%s hnum=%s 消除离线告警", addr, sn, hnum)
						}
					}
					farmid := gconv.String(item["farmid"])
					houseid := gconv.String(item["houseid"])
					remark := gconv.String(item["remark"])
					add_time := gconv.Int64(item["add_time_ts"])
					level := gconv.Int(item["level"])
					_type := gconv.Int(item["type"])
					subtype := gconv.Int(item["subtype"])
					cancel_time := gtime.Now().TimestampMilli()
					//添加到告警记录表
					dao.T_alarm.AddAlarm(farmid, houseid, model.DMXFGW_NODE, addr, sn, model.ALARM_LEVEL_1, "", "", model.ALARM_SWQ_OFFLINE, model.ALARM_CANCEL, remark)
					tdengine.InsertDeviceAlarmHis(
						farmid,
						houseid,
						model.DMXFGW_NODE,
						addr,
						sn,
						add_time,
						level,
						_type,
						subtype,
						model.ALARM_SWQ_OFFLINE,
						remark,
						model.ALARM_CANCEL,
						cancel_time,
						0,
						0,
						"")
				}
			}
		}

	}
	redis.SetGwNodeAliveVal(addr, sn, online)
}
func handNodeAlarm(addr string, sn string, alarm string) {
	//alarm状态字
	old_alarm := redis.GetGwNodeAlarm(addr, sn)

	//if sn == "944" {
	//	old_alarm = "0"
	//}

	if old_alarm != alarm {
		//告警变化
		alarm_content_arr := []string{
			model.ALARM_SWQ_FEED_BLOCKED,
			model.ALARM_SWQ_WATER_BLOCKED,
			model.ALARM_SWQ_AD_ERROR,
			model.ALARM_SWQ_TOUCH_ERROR,
			model.ALARM_SWQ_MOTOR_CUR_TOO_HIGH,
			model.ALARM_SWQ_WATER_ERROR,
			model.ALARM_SWQ_MOTOR_IDLING,
		}
		alarm_subtype_arr := []int{
			model.ALARM_SUBTYPE_SWQ_FEED_BLOCKED,
			model.ALARM_SUBTYPE_SWQ_WATER_BLOCKED,
			model.ALARM_SUBTYPE_SWQ_AD_ERROR,
			model.ALARM_SUBTYPE_SWQ_TOUCH_ERROR,
			model.ALARM_SUBTYPE_SWQ_MOTOR_CUR_TOO_HIGH,
			model.ALARM_SUBTYPE_SWQ_WATER_ERROR,
			model.ALARM_SUBTYPE_SWQ_MOTOR_IDLING,
		}
		//save_record_taos := g.Cfg().GetBool("basic.save_record_taos")
		//save_record_mysql := g.Cfg().GetBool("basic.save_record_mysql")

		for i := 0; i < len(alarm_content_arr); i++ {

			old_alarm_bit := common.GetBitOffset(gconv.Uint32(old_alarm), uint32(i))
			alarm_bit := common.GetBitOffset(gconv.Uint32(alarm), uint32(i))
			if old_alarm_bit != alarm_bit {
				alarm_content := alarm_content_arr[i]
				subtype := alarm_subtype_arr[i]

				if alarm_bit > 0 {
					//发生告警
					houseid, farmid, _ := dao.T_device.GetDeviceParentID(model.GW_MODE, addr)
					hnum := redis.GetGwNodeHnum(addr, sn)
					if hnum == "" {
						hnum, _ = dao.T_hzfgw_node_para.GetHnum(addr, sn)
					}
					item, _ := dao.T_alarm_now.GetNodeHappenAlarm(addr, sn, alarm_content)
					if item == nil {
						_, err := dao.T_alarm_now.AddAlarm(farmid, houseid, model.DMXFGW_NODE, addr, sn, model.ALARM_LEVEL_1, strconv.Itoa(model.ALARM_TYPE_SWQ), strconv.Itoa(subtype), alarm_content, hnum)
						if err != nil {
							g.Log().Line(true).Error(err)
						} else {
							g.Log().Line(true).Warningf("addr=%s sn=%s hnum=%s %s 告警发生", addr, sn, hnum, alarm_content)
						}
					} else {

					}

				} else {
					//取消告警
					item, err := dao.T_alarm_now.GetNodeHappenAlarm(addr, sn, alarm_content)
					if err != nil {
						g.Log().Line(true).Error(err)
					} else {
						//消除告警
						if item != nil {
							//清除当前告警表
							_, err := dao.T_alarm_now.Delete(gconv.String(item["id"]))
							if err != nil {
								g.Log().Line(true).Error(err)
							} else {
								g.Log().Line(true).Warningf("addr=%s sn=%s  %s 告警取消", addr, sn, alarm_content)
							}

							farmid := gconv.String(item["farmid"])
							houseid := gconv.String(item["houseid"])
							remark := gconv.String(item["remark"])
							add_time := gconv.Int64(item["add_time_ts"])
							level := gconv.Int(item["level"])
							_type := gconv.Int(item["type"])
							subtype := gconv.Int(item["subtype"])
							cancel_time := gtime.Now().TimestampMilli()
							//添加到告警记录表
							dao.T_alarm.AddAlarm(farmid, houseid, model.DMXFGW_NODE, addr, sn, model.ALARM_LEVEL_1, strconv.Itoa(model.ALARM_TYPE_SWQ), strconv.Itoa(subtype), alarm_content, model.ALARM_CANCEL, remark)
							tdengine.InsertDeviceAlarmHis(
								farmid,
								houseid,
								model.DMXFGW_NODE,
								addr,
								sn,
								add_time,
								level,
								_type,
								subtype,
								alarm_content,
								remark,
								model.ALARM_CANCEL,
								cancel_time,
								0,
								0,
								"")
						}
					}
				}
			}
		}

	}
	redis.SetGwNodeAlarm(addr, sn, alarm)
}

func handParamNew(pack *DMXFGW.Pack, addr string) {
	_, err := dao.T_device.GetOne(fmt.Sprintf(" device_addr='%s' ", addr))
	if err != nil {
		g.Log().Line(true).Debugf(" addr=%s not create topo!", addr)
		return
	}
	g.Log().Line(true).Infof("Gw %s handParamNew...", addr)
	para, err := DMXFGW.DecodeParam(pack.Data)
	if err != nil {
		g.Log().Line().Debug(err)
	} else {

		fpara := DMXFGW.FormatParam(para)
		isOk, err := dao.T_hzfgw_para.Save(addr, fpara)
		if !isOk {
			g.Log().Line(true).Error(err.Error())
		} else {
			g.Log().Line(true).Infof("save DMXFGW %s to t_hzfgw_para ok", addr)
		}

		//保存节点参数
		var needSaveNodeParas gdb.List
		//var needDeleteNodeParas gdb.List
		var needSaveNodeParaHis gdb.List

		node_num := 0

		nodeParas := fpara.NodeParas
		for i, node := range nodeParas {
			//g.Log().Line(true).Debugf("sn=%s modeEn=%d", node.Sn, gconv.Int(fpara.NodeEn[i]))

			if para.NodeParas[i].Mode == DMXFGW.MODE_FG || para.NodeParas[i].Mode < DMXFGW.MODE_GW {
				if gconv.Int(node.Sn) > 0 && gconv.Int(fpara.NodeEn[i]) > 0 {
					node_num++

					_model := ""
					mode := 0
					var json string
					if para.NodeParas[i].Mode < DMXFGW.MODE_GW {
						_model = "feed"
						json, _ = gparser.VarToJsonString(node)
						mode = gconv.Int(node.Mode)
					} else if para.NodeParas[i].Mode == DMXFGW.MODE_FG {
						_model = "fg"
						fg_node := fpara.NodeFgParas[i]
						json, _ = gparser.VarToJsonString(fg_node)
						mode = gconv.Int(fg_node.Mode)
					}

					sn := node.Sn
					tn := i
					enable := fpara.NodeEn[i]
					//data, _ := gparser.VarToJsonString(node)
					//son := node.Son

					crc16 := node.Crc16
					hnum := node.Hnum
					free_feed := 0

					items, err := dao.T_hzfgw_node_para.GetList(fmt.Sprintf(" addr='%s' and sn='%s' ", addr, sn))
					if err != nil {
						g.Log().Line(true).Error(err)
						break
					} else {
						item := gdb.Map{}
						//var old_data, new_data string
						if len(items) > 0 {
							//更新
							item = items[0]

							old_mode := gconv.Int(item["mode"])
							if old_mode != DMXFGW.FEED_MODE_LACTATION && mode == DMXFGW.FEED_MODE_LACTATION {
								//从其他阶段变为哺乳则修改仔猪为12
								item["son"] = 12
								item["pig_num"] = 1
							}
							if old_mode == DMXFGW.FEED_MODE_LACTATION && mode != DMXFGW.FEED_MODE_LACTATION {
								//哺乳阶段修改为其他阶段，修改仔猪=0
								item["son"] = 0
								item["pig_num"] = 1
							}

							//停用模式切换到保育/育肥，默认30只猪
							if mode == DMXFGW.FEED_MODE_NURSE || mode == DMXFGW.FEED_MODE_FAT {
								if old_mode == 0 {
									item["pig_num"] = 30
								}
							}

							//其他保持不变

						} else {
							//新增，新增时候默认 哺乳12仔猪，其他0
							if gconv.Int(node.Mode) == DMXFGW.FEED_MODE_LACTATION {
								item["pig_num"] = 1
								item["son"] = 12
							} else if gconv.Int(node.Mode) == DMXFGW.FEED_MODE_PG {
								item["pig_num"] = 1
								item["son"] = 0
							} else if gconv.Int(node.Mode) == DMXFGW.FEED_MODE_NURSE || gconv.Int(node.Mode) == DMXFGW.FEED_MODE_FAT {
								item["pig_num"] = g.Cfg().GetInt("ext_biz.default_fat_nurse_pig_num")
								item["son"] = 0
							}
						}
						item["addr"] = addr
						item["sn"] = sn
						item["model"] = _model
						item["tn"] = tn
						item["hnum"] = hnum
						item["enable"] = enable
						//item["son"] = son
						item["mode"] = mode
						item["data"] = json
						item["free_feed"] = free_feed
						item["crc16"] = crc16
						item["lasttime"] = gtime.Now().String()
						needSaveNodeParas = append(needSaveNodeParas, item)
					}
				}

			}
		}

		redis.SetGwNodeNum(addr, gconv.String(node_num))
		dao.T_hzfgw_node_para.Clear(addr) //强制刷新，删除一些已经停用的饲喂器
		if len(needSaveNodeParas) > 0 {
			isOk, err = dao.T_hzfgw_node_para.Save(needSaveNodeParas)
			if !isOk {
				g.Log().Line(true).Error(err.Error())
			} else {
				g.Log().Line(true).Infof("save DMXFGW %s num=%d to t_hzfgw_node_para ok", addr, len(needSaveNodeParas))
				//2024-03-04 保存当前猪只数目到redis
				for _, v := range needSaveNodeParas {
					sn := gconv.String(v["sn"])
					addr := gconv.String(v["addr"])
					pig_num := gconv.String(v["pig_num"])
					son := gconv.String(v["son"])
					hnum := gconv.String(v["hnum"])
					redis.SetGwNodeSonNum(addr, sn, son)
					redis.SetGwNodePigNum(addr, sn, pig_num)
					redis.SetGwNodeHnum(addr, sn, hnum)
				}
			}
		} else {
			g.Log().Line(true).Infof("save DMXFGW %s num=0 to t_hzfgw_node_para", addr)
		}

		if len(needSaveNodeParaHis) > 0 {
			_, err = dao.T_hzfgw_node_para_his.Save(needSaveNodeParaHis)
			if err != nil {
				g.Log().Line(true).Error(err)
			} else {
				g.Log().Line(true).Infof("save DMXFGW %s num=%d to t_hzfgw_node_para_his ok", addr, len(needSaveNodeParaHis))
			}
		}

	}
}

func handAllocateNew(pack *DMXFGW.Pack, addr string) {
	g.Log().Line(true).Infof("分配地址成功")
	//写入数据库
	var daddr uint32
	daddr = dao.GetMaxDaddr(model.GW_MODE) + 1
	db := g.DB(g.Cfg().GetString("custom.db_name"))
	sqlres, error := db.Model("t_allocate_device").Data(
		g.Map{
			"daddr":       daddr,
			"type":        model.GW_MODE,
			"create_date": gtime.Now().Format("y-m-d H:i:s"),
		}).Insert()
	if error != nil {
		g.Log().Line(true).Infof("保存t_allocate_device记录失败:%s", error.Error())
	} else {
		if a, err := sqlres.RowsAffected(); err == nil {
			g.Log().Line(true).Infof("新增t_allocate_device记录%d条", a)
		}
	}
}

func handRecordNew(pack *DMXFGW.Pack, addr string) {
	_, err := dao.T_device.GetOne(fmt.Sprintf(" device_addr='%s' ", addr))
	if err != nil {
		g.Log().Line(true).Debugf(" addr=%s not create topo!", addr)
		return
	}
	g.Log().Line(true).Debugf("Gw 记录返回")

	save_record_taos := g.Cfg().GetBool("basic.save_record_taos")
	save_record_mysql := g.Cfg().GetBool("basic.save_record_mysql")
	if len(pack.Data) == 0 {
		g.Log().Line(true).Debugf("Gw %s 无记录", addr)
	} else {
		var rec DMXFGW.Record
		rec_size := binary.Size(rec)
		dlen := gbinary.LeDecodeToUint16(pack.DLen[:])
		num := int(dlen) / rec_size
		g.Log().Line(true).Debugf("Gw %s 记录%d条", addr, num)
		if len(pack.Data) >= (num * rec_size) {
			//decode
			var f_rec_arr []DMXFGW.FRecord
			var f_rec_gw_arr []DMXFGW.FRecordGw
			for i := 0; i < num; i++ {
				tdata := pack.Data[(i * rec_size) : (i*rec_size)+rec_size]
				//判断模式
				if len(tdata) >= 30 {
					mode := tdata[29]

					if mode < DMXFGW.MODE_GW || mode == DMXFGW.MODE_FG {
						//饲喂器+粥料器记录
						s_rec := DMXFGW.DecodeRecord(tdata)
						f_rec := DMXFGW.FormatRecord(s_rec)
						f_rec_arr = append(f_rec_arr, *f_rec)
					} else if mode == 0xF0 {
						//网关记录
						s_rec := DMXFGW.DecodeRecordGw(tdata)
						f_rec := DMXFGW.FormatRecordGw(s_rec)
						f_rec_gw_arr = append(f_rec_gw_arr, *f_rec)
					}
				}
			}

			var list gdb.List
			var list_gw gdb.List
			//var needUpdateStatusList gdb.List
			now := gtime.Now().String()
			for _, v := range f_rec_arr {
				//判断时间是否正确
				if !common.IsDatetime(v.RecTime) {
					g.Log().Line(true).Warningf("Gw %s SN:%s RecTime = %s", addr, v.Sn, v.RecTime)
					v.RecTime = "1970-01-01 00:00:00"
				}

				//饲喂器
				//获取栏位号
				//hnum, _ := dao.T_hzfgw_node_para.GetHnum(addr, v.Sn)
				hnum := redis.GetGwNodeHnum(addr, v.Sn)
				if hnum == "" {
					hnum, _ = dao.T_hzfgw_node_para.GetHnum(addr, v.Sn)
				}
				item := gdb.Map{
					"rid":            v.Rid,
					"device_type":    model.DMXFGW,
					"addr":           addr,
					"sn":             v.Sn,
					"hnum":           hnum, //保存栏位号
					"rectime":        v.RecTime,
					"event":          v.Event,
					"feed_type":      v.FeedTypeBit,
					"begin_end":      v.BeginEndBit,
					"err":            v.HasErrBit,
					"mode":           v.Mode,
					"auto":           v.AutoBit,
					"tb":             v.HasTbBit,
					"remote":         v.RemoteBit,   //新终端
					"tb_record":      v.TbRecordBit, //新终端
					"food_left":      v.HasFoodBit,
					"max_feed":       v.MaxFeed,
					"amount":         v.Amount,
					"day_use_feed":   v.DayUseFeed,
					"day_use_water":  v.DayUseWater,
					"v22":            v.V22,
					"i41":            v.I41,
					"vin":            v.Vin,
					"vin_calc":       v.Vin_calc,
					"motor_vol":      v.Motor_vol,
					"motor_cur":      v.Motor_cur,
					"flag_mode":      v.FlagMode,     //新终端
					"flag_mode_bits": v.FlagModeBits, //新终端
					"flag1":          v.Flag1,        //新终端
					"cnt_gap_time":   v.CntGapTime,   //新终端
					"emergency":      v.Emergency,    //新终端
					"step_mr":        v.StepMR,       //新终端
					"debug":          v.Debug,
					"model":          v.Model,
					"readtime":       now,
				}
				list = append(list, item)

			}

			for _, v := range f_rec_gw_arr {
				item := gdb.Map{
					"rid":         v.Rid,
					"device_type": model.DMXFGW,
					"addr":        addr,
					"sn":          v.Sn,
					"rectime":     v.RecTime,
					"rtype":       v.Rtype,
					"readtime":    now,
				}
				g.Log().Line(true).Debug(item)
				item = gdb.Map{
					"rid":         v.Rid,
					"device_type": model.DMXFGW,
					"addr":        addr,
					"sn":          v.Sn,
					"rectime":     v.RecTime,
					"rtype":       v.Rtype,
					"readtime":    now,
				}
				list_gw = append(list_gw, item)
			}
			debug_mode := g.Cfg().GetBool("basic.DebugMode")
			if debug_mode {
				g.Log().Line(true).Infof("调试模式,不保存记录")
				return
			}

			if len(list) > 0 {
				for i := 0; i < len(list); i++ {
					item := list[i]

					if save_record_taos {
						//判断是否存在，存在保存

						/*_sn := gconv.Int(item["sn"])
						_rid := gconv.Int(item["rid"])
						if _rid > 0 && _sn > 0 {
							if !tdengine.ExitsRecord(gconv.String(item["sn"]), gconv.Uint32(item["rid"])) {
								//使用实时数据库保存
								isOk, err := tdengine.InsertRecordBak(item)
								if !isOk {
									g.Log().Line(true).Error(err.Error())
									continue
								}
							} else {
								g.Log().Line(true).Warningf(" sn=%s,rid=%d exist!", gconv.String(item["sn"]), gconv.Uint32(item["rid"]))
							}
						}*/

						//使用实时数据库保存
						/*if addr == "60004672" {
							isOk, err := tdengine.InsertRecord(item)
							if !isOk {
								g.Log().Line(true).Error(err.Error())
								continue
							}
						} else {
							isOk, err := tdengine.InsertRecordBak(item)
							if !isOk {
								g.Log().Line(true).Error(err.Error())
								continue
							}
						}*/
						isOk, err := tdengine.InsertRecord(item)
						if !isOk {
							g.Log().Line(true).Error(err.Error())
							continue
						}
						time.Sleep(30 * time.Millisecond)
					}
					if save_record_mysql {
						//使用mysql保存
						isOk, err := dao.T_hzfgw_record.SaveList(item)
						if !isOk {
							g.Log().Line(true).Error(err.Error())
							continue
						}
					}

					//更新最大rid
					rid := redis.GetGwLastRid(addr)
					if gconv.Uint64(rid) < gconv.Uint64(item["rid"]) {
						redis.SetGwLastRid(addr, gconv.String(item["rid"]))
					}

					//g.Log().Line(true).Infof("save DMXFGW %s to t_hzfgw_record ok", addr)

					//2024-03-27 每次下料结束记录写入需判断 数据是否比那天的status_his的内容更大，更大的的需要更新status_his的记录
					sn := gconv.String(item["sn"])
					begin_end := gconv.Int(item["begin_end"])
					feed_type := gconv.Int(item["feed_type"]) //0 下水 1 下料
					auto := gconv.Int(item["auto"])
					tb_record := gconv.Int(item["tb_record"])
					rectime := gconv.String(item["rectime"])
					day_use_feed := gconv.Float32(item["day_use_feed"])   //注意 这个是对应自动/手动 分开的汇总
					day_use_water := gconv.Float32(item["day_use_water"]) //注意 这个是对应自动/手动 分开的汇总
					//找到当天最大的手动和自动的day_use_feed/day_use_water

					his_date := gtime.NewFromStr(rectime).Format(common.DATE_FORMAT)
					var _day_use_feed_auto float32
					var _day_use_water_auto float32
					var _day_use_feed_man float32
					var _day_use_water_man float32

					if begin_end == 0 && feed_type == 1 && tb_record == 0 {
						//下料
						hisItem, _ := dao.T_hzfgw_node_status_his.GetOne(addr, sn, his_date)
						if hisItem != nil {
							day_use_feed_auto := gconv.Float32(hisItem["day_use_feed_auto"])
							day_use_feed_man := gconv.Float32(hisItem["day_use_feed_man"])

							mode := gconv.Int(hisItem["mode"])
							_model := gconv.String(hisItem["model"])
							day := gconv.Int(hisItem["day"])

							pig_num := gconv.Int(redis.GetGwNodePigNum(addr, sn))
							if pig_num == 0 && mode > 0 {
								if mode == DMXFGW.FEED_MODE_PG || mode == DMXFGW.FEED_MODE_LACTATION {
									pig_num = 1
								} else if mode == DMXFGW.FEED_MODE_NURSE || mode == DMXFGW.FEED_MODE_FAT {
									pig_num, _ = dao.T_hzfgw_node_para.GetPigNum(addr, sn)
								}
							}

							if auto > 0 {
								//自动下料
								_day_use_feed_auto = day_use_feed
								if day_use_feed_auto+0.01 < _day_use_feed_auto {
									day_use_feed_auto = _day_use_feed_auto //更新使用更大的记录值
									var avg_feed float32
									if pig_num > 0 {
										avg_feed = (day_use_feed_auto + day_use_feed_man) / float32(pig_num)
									}
									var health string
									var low, standard, high float32
									low, standard, high = GetStandard(_model, mode, gconv.Int(day))
									if low <= avg_feed && avg_feed <= high {
										//正常采食
										health = "good"
									} else {
										if low > avg_feed {
											//低采食
											health = "low"
										} else if high < avg_feed {
											//高采食
											health = "high"
										}
									}
									hisItem["pig_num"] = pig_num
									hisItem["avg_feed"] = avg_feed
									hisItem["low_standard"] = low
									hisItem["high_standard"] = high
									hisItem["standard"] = standard
									hisItem["health"] = health

									hisItem["day_use_feed_auto"] = day_use_feed_auto
									hisItem["day_use_feed_man"] = _day_use_feed_man
									hisItem["lasttime"] = common.NowString()
									hisItem["source"] = 1 //记录更新
									dao.T_hzfgw_node_status_his.Save(hisItem)
									g.Log().Line(true).Infof("更新 T_hzfgw_node_status_his 自动下料:his_date=%s,addr=%s,sn=%s,记录rid:=%s", his_date, addr, sn, rid)
								}
							} else {
								//手动下料
								_day_use_feed_man = day_use_feed
								if day_use_feed_man+0.01 < _day_use_feed_man {
									day_use_feed_man = _day_use_feed_man //更新使用更大的记录值
									var avg_feed float32
									if pig_num > 0 {
										avg_feed = (day_use_feed_auto + day_use_feed_man) / float32(pig_num)
									}
									var health string
									var low, standard, high float32
									low, standard, high = GetStandard(_model, mode, gconv.Int(day))
									if low <= avg_feed && avg_feed <= high {
										//正常采食
										health = "good"
									} else {
										if low > avg_feed {
											//低采食
											health = "low"
										} else if high < avg_feed {
											//高采食
											health = "high"
										}
									}
									hisItem["pig_num"] = pig_num
									hisItem["avg_feed"] = avg_feed
									hisItem["low_standard"] = low
									hisItem["high_standard"] = high
									hisItem["standard"] = standard
									hisItem["health"] = health

									hisItem["day_use_feed_auto"] = day_use_feed_auto
									hisItem["day_use_feed_man"] = _day_use_feed_man
									hisItem["lasttime"] = common.NowString()
									hisItem["source"] = 1 //记录更新
									dao.T_hzfgw_node_status_his.Save(hisItem)
									g.Log().Line(true).Infof("更新 T_hzfgw_node_status_his 手动下料 :his_date=%s,addr=%s,sn=%s,记录rid:=%s", his_date, addr, sn, rid)
								}

							}

						}
					} else if begin_end == 0 && feed_type == 0 && tb_record == 0 {
						//下水
						hisItem, _ := dao.T_hzfgw_node_status_his.GetOne(addr, sn, his_date)
						if hisItem != nil {
							day_use_water_auto := gconv.Float32(hisItem["day_use_water_auto"])
							day_use_water_man := gconv.Float32(hisItem["day_use_water_man"])

							if auto > 0 {
								//自动下水
								_day_use_water_auto = day_use_water
								if day_use_water_auto+0.01 < _day_use_water_auto {
									day_use_water_auto = _day_use_water_auto //更新使用更大的记录值
									hisItem["day_use_water_auto"] = day_use_water_auto
									hisItem["lasttime"] = common.NowString()
									hisItem["source"] = 1 //记录更新
									dao.T_hzfgw_node_status_his.Save(hisItem)
									g.Log().Line(true).Infof("更新 T_hzfgw_node_status_his 自动下水:his_date=%s,addr=%s,sn=%s,记录rid:=%s", his_date, addr, sn, rid)
								}
							} else {
								//手动下水
								_day_use_water_man = day_use_water
								if day_use_water_man+0.01 < _day_use_water_man {
									day_use_water_man = _day_use_water_man //更新使用更大的记录值
									hisItem["day_use_water_man"] = day_use_water_man
									hisItem["lasttime"] = common.NowString()
									hisItem["source"] = 1 //记录更新
									dao.T_hzfgw_node_status_his.Save(hisItem)
									g.Log().Line(true).Infof("更新 T_hzfgw_node_status_his 手动下水:his_date=%s,addr=%s,sn=%s,记录rid:=%s", his_date, addr, sn, rid)
								}
							}
						}

					}

				}

			}

			if len(list_gw) > 0 {
				for i := 0; i < len(list_gw); i++ {
					isOk, err := dao.T_hzfgw_record_gw.SaveList(list_gw[i])
					if !isOk {
						g.Log().Line(true).Error(err.Error())
					} else {
						//更新最大rid
						rid := redis.GetGwLastRid(addr)
						if gconv.Uint64(rid) < gconv.Uint64(list_gw[i]["rid"]) {
							redis.SetGwLastRid(addr, gconv.String(list_gw[i]["rid"]))
						}
					}
				}
			}

			//如果maxid-rid>50 读取下一组记录
			busy_flag := redis.GetGwBusyFlag(addr)
			if busy_flag > 0 && busy_flag < 5 {
				g.Log().Line(true).Warningf("Gw %s busy waiting......", addr)
				//busy_flag++
				//redis.SetGwBusyFlag(addr, busy_flag)
			} else {
				rid := redis.GetGwLastRid(addr)
				minid, maxid := redis.GetGwMinMaxRid(addr)
				if rid != "" && maxid != "" && minid != "" {
					_rid := gconv.Uint64(rid)
					_minid := gconv.Uint64(minid)
					_maxid := gconv.Uint64(maxid)
					g.Log().Line(true).Debugf("rid=%d minid=%d maxid=%d", _rid, _minid, _maxid)
					if _rid > _minid && (_maxid-_rid) > 50 {
						time.Sleep(100 * time.Millisecond)
						g.Log().Line(true).Debugf("readGwRecords next! addr=%s rid=%d num=%d", addr, _rid+1, 50)
						readNMSWQGWRecords(addr, _rid+1, 50)
					}
				}
				redis.SetGwBusyFlag(addr, 0)
			}

		} else {
			g.Log().Line(false).Errorf("len err:len(pack.Data)=%d num*rec_size=%d", len(pack.Data), num*rec_size)

		}
	}
}

func readNMSWQGWRecords(addr string, beginRid uint64, num int) {
	debug_mode := g.Cfg().GetBool("basic.DebugMode")
	if debug_mode {
		return
	}
	g.Log().Line(true).Debugf("Gw %s readRecords,beginRid:%d,num=%d", addr, beginRid, num)
	c := CreateMqttClient("cid_readNMSWQGWRecords_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Info(token.Error().Error())
	}

	var data []byte
	data = append(data, gbinary.LeEncodeUint32(uint32(beginRid))...)
	data = append(data, gbinary.LeEncodeUint16(uint16(num))...)
	size := len(data)
	g.Log().Line(true).Debugf("size=%d", size)
	msg := DMXFGW.CreateMsg(03, 5, uint32(size), data)
	g.Log().Line(true).Debugf("Gw send msg: % X", msg)

	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()
	c.Disconnect(250)
}

func ReadNMSWQGW_AllStatus() {
	/*clients_data, _ := GetApiClients(gdb.Map{
		"_page":          1,
		"_limit":         100000,
		"_like_clientid": "fmt.Sprintf("cid_%s_", model.GW_MODE)_",
	})*/
	c := CreateMqttClient("cid_ReadNMSWQGWStatus_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}

	//minute := gtime.Now().Minute()

	//改为只读取建立的档案的
	list, _ := dao.T_device.GetList(" device_type='DMXFGW' ")
	if len(list) > 0 {
		for _, item := range list {
			device_addr := gconv.String(item["device_addr"])

			//addr_int := gconv.Int(device_addr)
			if 1 == 1 {
				//addr_int%2 == minute%2
				//g.Log().Line(true).Debugf("minute=%d,addr_int=%d", minute, addr_int)

				topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, device_addr)
				//g.Log().Line(true).Debugf(topic_s)
				var data []byte
				var status DMXFGW.State
				size := binary.Size(status)
				//g.Log().Line(true).Infof("size=%d", size)
				msg := DMXFGW.CreateMsg(02, 0, uint32(size), data)
				g.Log().Line(true).Debugf("Gw send msg: % X", msg)
				// 发布消息
				token := c.Publish(topic_s, 0, false, msg)
				token.Wait()
			}

		}
	}
	/*for _, item := range clients_data {
		cid := gconv.String(item["clientid"])
		cid_arr := gstr.Split(cid, "_")
		addr := cid_arr[2]
		topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
		g.Log().Line(true).Info(topic_s)
		var data []byte
		var status DMXFGW.State
		size := binary.Size(status)
		//g.Log().Line(true).Infof("size=%d", size)
		msg := DMXFGW.CreateMsg(02, 0, uint32(size), data)
		g.Log().Line(true).Infof("Gw send msg: % X", msg)
		// 发布消息
		token := c.Publish(topic_s, 0, false, msg)
		token.Wait()
	}*/
	c.Disconnect(500)
}

// 读取网参数
func readNMSWQGWPara(addr string) {

	c := CreateMqttClient("cid_ReadNMSWQGWPara_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	//topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	//g.Log().Line(true).Info(topic_s)
	var data []byte
	var para DMXFGW.Para
	size := binary.Size(para)
	g.Log().Line(true).Debugf("size=%d", size)
	msg := DMXFGW.CreateMsg(0, 0, uint32(size), data)
	g.Log().Line(true).Debugf("Gw send msg: % X", msg)
	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()
	c.Disconnect(250)
}

func UpdateNMSWQGWOnline() {
	var needSaveDeviceList gdb.List
	var needSaveNodeList gdb.List

	clients_data, _ := GetApiClients(gdb.Map{
		"_page":          1,
		"_limit":         100000,
		"_like_clientid": fmt.Sprintf("cid_%s_", model.GW_MODE),
	})
	list, _ := dao.T_device.GetList(fmt.Sprintf(" device_type='%s'", model.GW_MODE))

	if len(list) > 0 {
		for _, v := range list {
			device_addr := gconv.String(v["device_addr"])
			online, ip_address := CheckClientsOnline(clients_data, device_addr)
			if !online {
				clients_data1, _ := GetApiClients(gdb.Map{
					"_page":          1,
					"_limit":         100000,
					"_like_clientid": fmt.Sprintf("cid_%s_", model.GW_MODE),
				})
				online, ip_address = CheckClientsOnline(clients_data1, device_addr)
				if !online {
					clients_data2, _ := GetApiClients(gdb.Map{
						"_page":          1,
						"_limit":         100000,
						"_like_clientid": fmt.Sprintf("cid_%s_", model.GW_MODE),
					})
					online, ip_address = CheckClientsOnline(clients_data2, device_addr)
				}
			}
			if online {
				v["alive"] = 1
				v["device_ip"] = ip_address
				v["lasttime"] = gtime.Now().String()

				alive := redis.GetGwAlive(device_addr)
				if !alive {
					//从离线变为在线，告警取消
					g.Log().Line(true).Debugf("Gw %s:back online!", device_addr)
					houseid, farmid, err := dao.T_device.GetDeviceParentID(model.GW_MODE, device_addr)
					if err != nil {
						g.Log().Line(true).Error(err)
					} else {
						item, err := dao.T_alarm_now.GetHappenAlarm(gconv.String(farmid), gconv.String(houseid), model.GW_MODE, device_addr, model.ALARM_GW_OFFLINE)
						if err != nil {
							g.Log().Line(true).Error(err)
						} else {
							//消除告警
							if item != nil {
								_, err := dao.T_alarm_now.Delete(gconv.String(item["id"]))
								if err != nil {
									g.Log().Line(true).Error(err)
								} else {
									g.Log().Line(true).Debugf("addr=%s 消除离线告警", device_addr)
								}
							}
							farmid := gconv.String(item["farmid"])
							houseid := gconv.String(item["houseid"])
							remark := gconv.String(item["remark"])
							add_time := gconv.Int64(item["add_time_ts"])
							level := gconv.Int(item["level"])
							_type := gconv.Int(item["type"])
							subtype := gconv.Int(item["subtype"])
							cancel_time := gtime.Now().TimestampMilli()
							//添加到告警记录表
							dao.T_alarm.AddAlarm(farmid, houseid, model.DMXFGW, device_addr, "", model.ALARM_LEVEL_1, strconv.Itoa(_type), strconv.Itoa(subtype), model.ALARM_GW_OFFLINE, model.ALARM_CANCEL, remark)
							tdengine.InsertDeviceAlarmHis(
								farmid,
								houseid,
								model.DMXFGW,
								device_addr,
								"",
								add_time,
								level,
								_type,
								subtype,
								model.ALARM_GW_OFFLINE,
								remark,
								model.ALARM_CANCEL,
								cancel_time,
								0,
								0,
								"")

						}
					}
				}

				redis.SetGwAlive(device_addr, true)
				needSaveDeviceList = append(needSaveDeviceList, v)
			} else {
				v["alive"] = 0
				needSaveDeviceList = append(needSaveDeviceList, v)
				//更新终端离线
				alive := redis.GetGwAlive(device_addr)
				if alive {
					//从在线变为离线，告警产生
					g.Log().Line(true).Debugf("Gw %s:offline!!", device_addr)
					houseid, farmid, err := dao.T_device.GetDeviceParentID(model.GW_MODE, device_addr)
					if err != nil {
						g.Log().Line(true).Error(err)
					} else {
						_, err := dao.T_alarm_now.AddAlarm(farmid, houseid, DMXFGW.MODE, device_addr, "", model.ALARM_LEVEL_1, strconv.Itoa(model.ALARM_TYPE_GW), strconv.Itoa(model.ALARM_SUBTYPE_GW_OFFLINE), model.ALARM_GW_OFFLINE, "")
						if err != nil {
							g.Log().Line(true).Error(err)
						} else {
							g.Log().Line(true).Debugf("Gw=%s  终端离线告警", device_addr)
						}
					}
				}

				redis.SetGwAlive(device_addr, false)

				//把所有对应的饲喂器状态更新为离线
				//todo
				nodes, err := dao.T_hzfgw_node_status.GetList(fmt.Sprintf(" addr=%s ", device_addr))
				if err != nil {
					g.Log().Line().Error(err)
				} else {
					if len(nodes) > 0 {
						for _, node := range nodes {
							sn := gconv.String(node["sn"])

							node["online"] = 0
							redis.SetGwNodeAlive(device_addr, sn, false)
							needSaveNodeList = append(needSaveNodeList, node)
						}
					}
				}
			}

		}
	}

	if len(needSaveDeviceList) > 0 {
		isOk, err := dao.T_device.Save(needSaveDeviceList)
		if !isOk {
			g.Log().Line(true).Error(err.Error())
		} else {
			g.Log().Line(true).Debug("UpdateOnline list to t_device ok")
		}
	}

	if len(needSaveNodeList) > 0 {
		isOk, err := dao.T_hzfgw_node_status.Update(needSaveNodeList)
		if !isOk {
			g.Log().Line(true).Error(err.Error())
		} else {
			g.Log().Line(true).Debug("UpdateOnline to t_hzfgw_node_status ok")
		}
	}
}

func CheckClientsOnline(clients_data []map[string]interface{}, device_addr string) (alive bool, ip_address string) {
	alive = false
	for _, v := range clients_data {
		clientid := gconv.String(v["clientid"])
		clientid_arr := gstr.Split(clientid, "_")
		addr := clientid_arr[2]
		if addr == device_addr {
			alive = true
			ip_address = gconv.String(v["ip_address"])
			break
		}
	}
	return alive, ip_address
}

func GetAllNMSWQGWClock() {
	hour := gtime.Now().Hour()
	if hour == 23 || hour == 0 {
		return
	}
	g.Log().Line(true).Info("GetAllNMSWQGWClock")
	clients_data, _ := GetApiClients(gdb.Map{
		"_page":          1,
		"_limit":         100000,
		"_like_clientid": fmt.Sprintf("cid_%s_", model.GW_MODE),
	})
	c := CreateMqttClient("cid_ReadNMSWQGWStatus_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	for _, item := range clients_data {
		cid := gconv.String(item["clientid"])
		cid_arr := gstr.Split(cid, "_")
		addr := cid_arr[2]
		go func() {
			res := GetGwClock(addr)

			if gconv.Int(res["code"]) != 0 {
				g.Log().Line(true).Warning(res)
			} else {
				data := gconv.String(res["data"])
				date := gtime.NewFromStr(data)
				if date == nil {
					go SetNMSWQGWClock(addr)
				} else {
					now := gtime.Now()
					dur := now.Sub(date)
					g.Log().Line(true).Debugf("now:%s,device clock:%s,dur=%s", now.String(), data, dur.String())
					absValue := math.Abs(dur.Minutes())
					if absValue > 5 {
						g.Log().Line(true).Warningf(fmt.Sprintf("%s clock need sync!", addr))
						go SetNMSWQGWClock(addr)
					}
				}

			}
		}()

	}
	c.Disconnect(500)
}

func SetNMSWQGWClock(addr string) (res g.Map) {
	res = g.Map{}
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	g.Log().Line(false).Debug(topic_s)

	//校时日期
	var datetime = gtime.Now().Format("ymdHis")
	clock, _ := hex.DecodeString(datetime)

	var data []byte
	data = append(data, clock...)
	size := len(data)
	msg := DMXFGW.CreateMsg(03, 0x01, uint32(size), data)
	g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack
	var err error
	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x83 && Addr == 0x01 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 20; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				res["code"] = -1
				res["msg"] = err.Error()
			} else {
				if pack.Data[0] == 0x00 {
					res["code"] = 0
					res["msg"] = "设置成功"
				} else {
					res["code"] = -1
					res["msg"] = "设置失败"
				}
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		res["code"] = -1
		res["msg"] = "请求超时,设备可能已经离线"
	}
	return res
}

func GetGwClock(addr string) (res g.Map) {
	res = g.Map{}
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	//g.Log().Line(false).Debug(topic_s)

	var data []byte
	size := 0
	msg := DMXFGW.CreateMsg(03, 0, uint32(size), data)
	//g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack
	var err error
	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		//topic := msg.Topic()
		//topic_arr := gstr.Split(topic, "/")
		//addr := topic_arr[2]
		//g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		//g.Log().Line(false).Println("Gw recv from addr:" + addr)
		//g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x83 && Addr == 0 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 20; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				res["code"] = -1
				res["msg"] = err.Error()
			} else {
				res["code"] = 0
				res["data"] = fmt.Sprintf("20%02X-%02X-%02X %02X:%02X:%02X", pack.Data[0], pack.Data[1], pack.Data[2], pack.Data[3], pack.Data[4], pack.Data[5])
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		res["code"] = -1
		res["msg"] = "请求超时,设备可能已经离线"
	}

	return res
}

func ClearNodeAlarm(addr string, sn string) {
	redis.SetGwNodeAlarm(addr, sn, "0")
	db := g.DB(g.Cfg().GetString("custom.db_name"))
	db.Exec(fmt.Sprintf("UPDATE t_hzfgw_node_status set alarm=0 WHERE sn='%s';", sn))
}

func LoadFeedStandard() {
	g.Log().Line(true).Info("LoadFeedStandard")
	db := g.DB(g.Cfg().GetString("custom.db_name"))
	sql := fmt.Sprintf(`select * from b_dfxw_feed_standard ORDER BY mode,day ASC;`)
	res, err := db.GetAll(sql)
	if err != nil {
		g.Log().Line(true).Error(err)
		return
	}
	model.DFXW_FEED_STANDARD_DATA = res.List()
	//g.Log().Line(true).Debug(model.DFXW_FEED_STANDARD_DATA)
}

// SetNodeSn 单独设置饲喂器新的SN
func SetNodeSn(addr string, tn int, sn string) (isOK bool, err error) {
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	g.Log().Line(true).Debug(topic_s)
	_begin := common.GetStructFieldOffset(DMXFGW.NodePara{}, "Sn")
	var t_buff = gbinary.LeEncodeUint32(gconv.Uint32(sn))

	var data []byte
	data = append(data, uint8(tn))
	data = append(data, uint8(1))
	data = append(data, t_buff...)
	//data = append(data, crc16_bytes...) //crc16
	size := uint32(len(data))
	msg := DMXFGW.CreateMsg(0x06, uint32(_begin), size, data)
	//g.Log().Line(true).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack

	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		//g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x86 && Addr == uint16(_begin) {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 200; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false
			} else {
				_tn := pack.Data[0]
				_res := pack.Data[1]
				if int(_tn) == tn {
					if _res == 0 {
						isOK = true
					} else {
						isOK = false
						err = errors.New("设置失败")
					}
				} else {
					isOK = false
					err = errors.New("设置失败:tn错误")
				}

			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}

	return isOK, err
}

func UpdateNMSWQGW(addr string, filename string) (isOK bool, err error) {
	//filename := "upHZFGW_N0106"

	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	g.Log().Line(false).Debug(topic_s)

	var data []byte
	//设置固件
	//host:=r.Request.Host
	//url:=fmt.Sprintf("http://%s/public/firmware/%s",host,filename)
	//url := fmt.Sprintf("http://192.168.0.114:9901/firmware/%s", filename)
	url := fmt.Sprintf("%s%s", g.Cfg().GetString("basic.firmware"), filename)
	//url:="http://120.79.248.26/files/"+filename
	g.Dump(url)
	data = append(data, gbinary.LeEncodeString(url)...)
	size := len(data)
	msg := DMXFGW.CreateMsg(03, 0x02, uint32(size), data)
	g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack

	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x83 && Addr == 0x02 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 50; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false
			} else {
				if pack.Data[0] == 0x00 {
					isOK = true
				} else {
					isOK = false
					err = errors.New("固件下发失败")
				}
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}

	return isOK, err
}

func UpdateNode(addr string, sn string, filename string) (isOK bool, err error) {
	//filename := "upHZFGW_N0106"

	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	g.Log().Line(false).Debug(topic_s)

	var data []byte
	//设置固件
	_addr := gconv.Uint32(sn)
	if _addr == 0 {
		data = append(data, 0x41)
		data = append(data, 0x27)
		data = append(data, 0x21)
		data = append(data, 0xF8)
	} else {
		data = append(data, gbinary.LeEncodeUint32(_addr)...)
	}

	//设置固件
	//host:=r.Request.Host
	//url:=fmt.Sprintf("http://%s/public/firmware/%s",host,filename)
	//url := fmt.Sprintf("http://192.168.0.114:9901/firmware/%s", filename)
	url := fmt.Sprintf("%s%s", g.Cfg().GetString("basic.firmware"), filename)
	//url:="http://120.79.248.26/files/"+filename
	g.Dump(url)
	data = append(data, gbinary.LeEncodeString(url)...)
	size := len(data)
	msg := DMXFGW.CreateMsg(03, 0x03, uint32(size), data)
	g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack

	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x83 && Addr == 0x03 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 50; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false
			} else {
				if pack.Data[0] == 0x00 {
					isOK = true
				} else {
					isOK = false
					err = errors.New("固件下发失败")
				}
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}

	return isOK, err
}

func SetNodeParam(addr string, tn int, params string, feed_plan_id int) (isOK bool, err error) {
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	g.Log().Line(true).Debug(topic_s)

	fp := DMXFGW.FNodePara{}
	gjson.DecodeTo(params, &fp)
	//g.Log().Line().Debug(fp)

	buff := DMXFGW.EncodeNodeParam(&fp)
	pid := gbinary.LeEncodeUint32(DMXFGW.PID)
	_begin := common.GetStructFieldOffset(DMXFGW.NodePara{}, "Sn")
	_end := common.GetStructFieldOffset(DMXFGW.NodePara{}, "Crc16")

	var buff_calc_crc16 []byte
	buff_calc_crc16 = append(buff_calc_crc16, pid...)
	buff_calc_crc16 = append(buff_calc_crc16, buff[_begin:_end]...)
	crc16 := common.CheckSum(buff_calc_crc16)
	crc16_bytes := gbinary.LeEncodeUint16(crc16)

	//_len := _end - _begin + 2 //2=crc16
	//g.Log().Line(true).Debugf("_begin=%d  _end =%d size=%d", _begin, _end, _len)

	var data []byte
	data = append(data, uint8(tn))
	data = append(data, uint8(1))
	data = append(data, buff[_begin:_end]...)
	data = append(data, crc16_bytes...) //crc16
	size := uint32(len(data))
	msg := DMXFGW.CreateMsg(0x05, 0, size, data)
	//g.Log().Line(true).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack
	//var err error
	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		//g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x85 && Addr == 0x00 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 200; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false

			} else {
				_tn := pack.Data[0]
				_res := pack.Data[1]
				if int(_tn) == tn {
					if _res == 0 {
						time.Sleep(1 * time.Second)
						//设置成功，保存方案，保存参数变更记录
						items, err := dao.T_hzfgw_node_para.GetList(fmt.Sprintf(" addr='%s' and tn=%d", addr, tn))
						if err != nil {
							g.Log().Line(true).Error(err)
						} else {
							if len(items) > 0 {
								item := items[0]
								item["data"] = params
								if feed_plan_id > 0 {
									item["feed_plan_id"] = feed_plan_id
								}
								_, err = dao.T_hzfgw_node_para.Save(item)
								if err != nil {
									g.Log().Line(true).Error(err)
								} else {
									g.Log().Line(true).Debugf("update T_hzfgw_node_para addr='%s' sn=%s feed_plan_id=%d ok", addr, gconv.String(item["sn"]), feed_plan_id)
								}

								//old_data := gconv.String(item["data"])
								new_data := params
								para_his := gdb.Map{
									"feed_plan_id": feed_plan_id,
									"addr":         addr,
									"sn":           gconv.String(item["sn"]),
									//"old_data":    old_data,
									"new_data":    new_data,
									"his_date":    gtime.Now(),
									"update_user": "replace user",
								}

								_, err = dao.T_hzfgw_node_para_his.Save(para_his)
								if err != nil {
									g.Log().Line(true).Error(err)
								} else {
									g.Log().Line(true).Debugf("add t_hzfgw_node_para_his addr='%s' sn=%s ok", addr, gconv.String(item["sn"]))
								}
							}
						}
						isOK = true
					} else {
						isOK = false
						err = errors.New("设置失败")
					}
				} else {
					isOK = false
					err = errors.New("设置失败:tn错误")
				}

			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}

	return isOK, err
}

func SetGwParam(addr string, params string) (isOK bool, err error) {

	fp := DMXFGW.FPara{}
	gjson.DecodeTo(params, &fp)

	isOK, err = SetGwNodesEn(addr, fp)

	if isOK {
		g.Log().Line(true).Debugf(" SetGwNodesEn OK")
		for i := 0; i < 100; i++ {
			time.Sleep(50 * time.Millisecond)
			if fp.NodeParas[i].Sn == "" || fp.NodeParas[i].Sn == "0" {
				continue
			}
			isOK, err = SetGwNodesPara(addr, fp, i)
			if !isOK {
				isOK = false
				err = errors.New(fmt.Sprintf("设置失败 block_idx=%d", i))
				break
			} else {
				g.Log().Line(true).Debugf(" SetGwNodesPara block_idx=%d OK", i)
			}
		}
	}
	return isOK, err
}

func SetGwNodesEn(addr string, fp DMXFGW.FPara) (isOK bool, err error) {
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	buff := DMXFGW.EncodeParam(&fp)
	var data []byte
	_begin := common.GetStructFieldOffset(DMXFGW.Para{}, "NodeEn")
	_end := common.GetStructFieldOffset(DMXFGW.Para{}, "NodeParas")
	_len := _end - _begin
	g.Log().Line(false).Debugf("_begin=%d  _end =%d size=%d", _begin, _end, _len)

	data = append(data, buff[_begin:_end]...)

	size := uint32(_len)
	msg := DMXFGW.CreateMsg(0x01, uint32(_begin), size, data)
	g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack

	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			//Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x81 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 50; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false
			} else {
				if pack.Data[0] == 0x00 {
					isOK = true
				} else {
					isOK = false
					err = errors.New("设置失败")
				}
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}
	return isOK, err
}
func SetGwNodesPara(addr string, fp DMXFGW.FPara, block_idx int) (isOK bool, err error) {
	c := CreateMqttClient("cid_Hub_"+grand.Digits(10), nil, nil)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		//panic(token.Error())
		g.Log().Line(true).Error(token.Error().Error())
	}
	topic_s := fmt.Sprintf("S/%s/%s", model.GW_MODE, addr)
	topic_p := fmt.Sprintf("P/%s/%s", model.GW_MODE, addr)
	buff := DMXFGW.EncodeParam(&fp)

	var node_para DMXFGW.NodePara
	node_size := binary.Size(node_para)

	var data []byte
	_begin := common.GetStructFieldOffset(DMXFGW.Para{}, "NodeParas") + int64(block_idx*node_size)
	_end := _begin
	for i := 0; i < 2; i++ {
		_end += int64(node_size)
	}
	_len := _end - _begin
	g.Log().Line(false).Debugf("_begin=%d  _end =%d size=%d", _begin, _end, _len)
	data = append(data, buff[_begin:_end]...)

	size := uint32(_len)
	msg := DMXFGW.CreateMsg(0x01, uint32(_begin), size, data)
	g.Log().Line(false).Debugf("Gw send msg: % X", msg)

	// 订阅主题
	var iscallback bool
	var pack *DMXFGW.Pack

	//var mqtt_msg mqtt.Message
	if token := c.Subscribe(topic_p, 0, func(c mqtt.Client, msg mqtt.Message) {
		topic := msg.Topic()
		topic_arr := gstr.Split(topic, "/")
		addr := topic_arr[2]
		g.Log().Line(false).Println("Gw recv from topic:" + msg.Topic())
		g.Log().Line(false).Println("Gw recv from addr:" + addr)
		g.Log().Line(false).Debugf("Gw recv msg: % X", msg.Payload())
		//解析返回
		pack, err = DMXFGW.DecodePack(msg.Payload())
		if err == nil {
			//Addr := gbinary.LeDecodeToUint16(pack.Addr[:])
			if pack.C == 0x81 {
				iscallback = true
				//mqtt_msg = msg
			}
		} else {
			iscallback = true
		}

	}); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
	}

	// 发布消息
	token := c.Publish(topic_s, 0, false, msg)
	token.Wait()

	for i := 0; i <= 50; i++ {
		if iscallback {
			//解析返回
			if err != nil {
				isOK = false
			} else {
				if pack.Data[0] == 0x00 {
					isOK = true
				} else {
					isOK = false
					err = errors.New("设置失败")
				}
			}
			break
		} else {
			//继续等待
			time.Sleep(100 * time.Millisecond)
		}
	}
	c.Disconnect(250)
	if !iscallback {
		isOK = false
		err = errors.New("请求超时,设备可能已经离线")
	}
	return isOK, err
}
